(* ===================================================================== *)
(** Application bop2vlog : traduction d'une net-list .bop vers une net-list
    en verilog, pour permettre ensuite l'utilisation d'outils tiers vers la synthese.
    @author Mathieu Barbin
    @version 1.0.* *)
(* ===================================================================== *)

open Bplib

open Inputtools
open Cdsmaker
open Vlogtools


(** 
    Contrairement à bop2c, bop2vlog travaille au niveau de la net-list .bop
    uniquement. Les net-list précompilée bpx ne sont pas exploitable.
    
    La raison est d'assurer une certaine cohérence entre les 2 fichiers, 
    en essayant de préserver les identifiants choisis dans le fichier .bop,
    et le découpage par blocs.

    On ne fait pas forcément toutes les vérifications 
    de contexte, ou l'analyse statique pendant cette traduction. 
    Il est entendu que l'utilisateur peut faire des simulations avec bopsimu, 
    avant d'utiliser bop2vlog.

    Les fichiers produits nécessitent peut etre une relecture mano avant de 
    pouvoir bien fonctionner sous iverilog, cver, modelsim, etc ... (voir tutos)

    On produit du code non-generique pour une plus grande portabilite (cver, IcarusV ne supportent
    pas les generics...)

    Dans le cas des memoires ram ou rom : on cree un fichier .list de l'etat de la memoire 
    a charger, ce format etant pris en charge par les simulateurs et synthetiseurs verilog.

    La traduction va creer une librairie verilog sous la forme d'un fichier unique contenant
    toutes les definitions necessaires a la simulation du bloc main.

    On creer egalement 1 fichier test bench par bloc signale candidat par l'attribut, ainsi que 
    pour le bloc main de la net-list bebop.

    Pour tout mettre dans le meme dossier, utiliser l'option -d. 
*)

(* ===================================================================== *)
(** Creer un dossier non existant *)
(* ===================================================================== *)
exception ISFILE of string

(* ===================================================================== *)
(** Dans la version 3.09, Sys.is_directory n'est pas implemente *)
(* ===================================================================== *)
let sys_is_directory dirname =
  try
    ignore (Sys.readdir dirname);
    true
  with
  | Sys_error _ -> false

let safe_mkdir dirname =
  if Sys.file_exists dirname 
  then
    if sys_is_directory dirname then ()
    else raise (ISFILE dirname)
  else
    Unix.mkdir dirname 0o755;;
(* ===================================================================== *)

(* ===================================================================== *)
(** Le type derivant des arguments d'execution *)
(* ===================================================================== *)
type execution =
    {
      overwrite : bool;
      folder : string option;
      filename : string;
      verbose : bool
    }
(* ===================================================================== *)

(* ===================================================================== *)
(** Exeption levee quand la ligne de commande est incoherente *)
exception Arguments_Invalides
(* ===================================================================== *)

(* ===================================================================== *)
(** Deduire d'un tableau Sys.argv le type d'execution
    Lever l'exeption Arguments_Invalides si la lecture echoue *)
(* ===================================================================== *)
let execution_of_argv argv =
  let overwrite = ref false 
  and verbose = ref false
  and folder = ref None
  and folder_done = ref false
  and filename = ref ""
  and filename_done = ref false
  in
  let len = Array.length argv in
  if len < 2 || len > 6 then raise Arguments_Invalides
  else
    let i = ref 1 in
    begin
      while (!i < len) do
	(
	  match argv.(!i) with
	  | "" -> ()
	  | "-f" -> overwrite := true
	  | "-v" -> 
	      begin
		Message.message#verbose_actif;
		verbose := true
	      end
	  | "-d" -> (* lire le prochain mot qui est le dossier cible *)
              if (!i = pred len) || !folder_done
              then raise Arguments_Invalides 
              else 
                begin
		  folder_done := true;
                  incr i;
                  folder := Some argv.(!i)
                end  
	  | o -> 
	      if o.[0] = '-' 
	      then
		begin
		  Printf.fprintf stderr " Unknown option '%s'\n" o;
		  flush stderr
		end
	      else
		if !filename_done then raise Arguments_Invalides 
		else 
		  begin
		    filename_done := true;
		    filename := o
		  end
	);
	incr(i)
      done;
      if not (!filename_done) then raise Arguments_Invalides
      else
	{
	  overwrite = !overwrite;
	  folder = !folder;
	  filename = !filename;
	  verbose = !verbose 
	}
    end
(* ===================================================================== *)


(* ===================================================================== *)
(** Faire la traduction, et enregistrer selon les modalites d'execution *)
(* ===================================================================== *)
let bop2vlog_of_execution exec = 
  let init = arite_primitive_initiale () in
  let prim_comp, t_analyse = analyse_net_list init exec.filename in
  (* creation de l'environnement *)
  let memory_arite = memory_arite_of_export_memoire t_analyse.y_table_export_memoires in
  let vlog_traduction, benches = environnement_and_benches_verilog prim_comp memory_arite t_analyse.y_fcts_debouclees in
  (* On commence la production des fichiers *)
  (* Gerer si on veut un dossier separé *)
  (
    match exec.folder with
    | None -> ()
    | Some f -> Printf.fprintf stderr "Working directory : %s\n" f; safe_mkdir f; Unix.chdir f
  );
  Printf.fprintf stderr "Generated files :\n";
  flush stderr;
  (* 1) Fichier .list : mémoires *)
  let f_mem_iter dec =
    let file_dest = list_file_name_of_type_memoire dec.type_dcl_m in
    match dec.description_entree_dcl_m with
    | None -> ()
    | Some code ->
	let create_file () =
	  BoolMatrix.to_asci_file code file_dest;
	  Printf.fprintf stderr " |-> \"%s\"\n" file_dest;
	  flush stderr
	in
	if not (Sys.file_exists file_dest) || exec.overwrite
	then create_file ()
	else
	  begin
	    Printf.fprintf stderr " [!] Cannot create \"%s\", file already exists\n" file_dest;
	    flush stderr
	  end
  in Array.iter f_mem_iter t_analyse.y_table_export_memoires;
  (* 2) Librairie commune *)
  let file, ext = split_file exec.filename in
  let file_lib = file^".v" in
  if (Sys.file_exists file_lib) && not exec.overwrite
  then
    begin
      Printf.fprintf stderr " [!] Cannot create \"%s\", file already exists\n" file_lib;
      flush stderr
    end
  else 
    begin
      (* creation du fichier principal *)
      Printf.fprintf stderr " |-> \"%s\"\n" file_lib;
      flush stderr;
      production_verilog_lib t_analyse vlog_traduction file_lib
    end;
  (* 3) Tests Benches *)
  let f_iter bench =
    let real_name = "vlog"^(String.capitalize bench)^".v" in
    if (Sys.file_exists real_name) && not exec.overwrite
    then
      begin 
	Printf.fprintf stderr " [!] Cannot create \"%s\", file already exists\n" real_name;
	flush stderr
      end
    else
      begin
	(* creation d'un fichier bench *)
	Printf.fprintf stderr " |-> \"%s\"\n" real_name;
	flush stderr;
	production_test_bench vlog_traduction file_lib bench real_name 
      end
  in 
  List.iter f_iter benches
(* ===================================================================== *)



let _ =  
  Message.message#thisis "bop2vlog";
  Message.message#verbose_desactif;
  try
    (
      let execution = execution_of_argv Sys.argv in
      bop2vlog_of_execution execution
    )
  with
  | Arguments_Invalides ->
      Printf.fprintf stderr "use : %s my_file.bop [-f -v] [-d mydirectory]\n" Sys.argv.(0);
      flush stderr
  | ISFILE f ->
      Printf.fprintf stderr "[!] Cannot create folder \"%s\" : a file already exists\n" f;
      flush stderr
